package br.unip.tcc.domotic.trash;

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;
 
import java.util.Set;
import java.util.HashSet;
import java.io.IOException;
import java.lang.annotation.Annotation;
 
public class AnnotatedClassFinder {
	
    private String basePackage;
    private ResourcePatternResolver resourceResolver = null;
    private MetadataReaderFactory metadataReaderFactory = null;
    private TypeFilter annotationFilter = null;
 
    public AnnotatedClassFinder(String basePackage) {
        this.basePackage = basePackage;
        this.resourceResolver = new PathMatchingResourcePatternResolver(Thread.currentThread().getContextClassLoader());
        this.metadataReaderFactory = new SimpleMetadataReaderFactory();
    }
 
    public static AnnotatedClassFinder getInstance(String basePackage){
        return new AnnotatedClassFinder(basePackage);
    }
 
    public Set<Class<?>> findByAnnotation(Class<? extends Annotation> annotation) {
        this.annotationFilter = new AnnotationTypeFilter(annotation);
        Set<Class<?>> annotatedClasses = new HashSet<Class<?>>();
        /*
        * First of all we load all resources that are under a specific package by using a ResourcePatternResolver.
        * By doing so we will use class files as simple resources without passing
        * by the ClassLoader which can for example cause the execution of a static initialization block.
        * It resolves also transparently resources in jars.
        */
        String candidateClassesLocationPattern = "classpath*:" + basePackage + "/**/*.class";
        Resource[] resources = null;
        try {
            resources = resourceResolver.getResources(candidateClassesLocationPattern);
        } catch (IOException e) {
            throw new RuntimeException(
                    "An I/O problem occurs when trying to resolve ressources matching the pattern : "
                            + candidateClassesLocationPattern, e);
        }
 
        /*
        * then we proceed resource by resource, using a MetadataReaderFactory to create
        * MetadataReader wich hides the ASM related interface and complexity.
        *
        */
        for (Resource resource : resources) {
            MetadataReader metadataReader = null;
            try {
                metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
 
                if (this.annotationFilter.match(metadataReader, metadataReaderFactory)) {
 
                    /*
                    * the AnnotationMetadata is a simple abstaction of the informations
                    * that holds the annotation
                    */
                    String className = convertResourceToClassName(resource, basePackage);
                    try {
                    	annotatedClasses.add(Class.forName(className));
                    	//annotatedClasses.add(ClassUtils.forName(className));
                    } catch (Exception e) {
                        throw new RuntimeException("problems occurs when trying to load the annotated class : " + className, e);
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException("An I/O problem occurs when trying to process resource : " + resource, e);
            }
        }
 
        return annotatedClasses;
    }
 
    public static String convertResourceToClassName(Resource resource, String basePackage) throws IOException {
        String path = resource.getFile().getPath();
        String pathWithoutSuffix = path.substring(0, path.length() - ClassUtils.CLASS_FILE_SUFFIX.length());
        String relativePathWithoutSuffix = "";
        if(System.getProperty("file.separator").equals("\\")) {
            relativePathWithoutSuffix = pathWithoutSuffix.substring(pathWithoutSuffix.indexOf(basePackage.replace('/', '\\')));
            relativePathWithoutSuffix = relativePathWithoutSuffix.replace('\\', '/');
        } else if(System.getProperty("file.separator").equals("//")) {
            relativePathWithoutSuffix = pathWithoutSuffix.substring(pathWithoutSuffix.indexOf(basePackage));
        } else {
            throw new RuntimeException("File separator is not recognized");
        }
 
        // taking out extra \ or /
        relativePathWithoutSuffix = relativePathWithoutSuffix.substring(1);
 
        return relativePathWithoutSuffix.replace('/', '.');
    }
} 

